﻿#region Internals
$unattendedXmlDefaultContent2012 = @'
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
  <settings pass="generalize">
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <DoNotCleanTaskBar>true</DoNotCleanTaskBar>
    </component>
    <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <SkipRearm>1</SkipRearm>
    </component>
  </settings>
  <settings pass="specialize">
	<component name="Security-Malware-Windows-Defender" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
		<DisableAntiSpyware>true</DisableAntiSpyware>
	</component>
    <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <Identification>
        <JoinWorkgroup xmlns="">NET</JoinWorkgroup>
      </Identification>
    </component>
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <ComputerName>SERVER</ComputerName>
      <RegisteredOrganization>vm.net</RegisteredOrganization>
      <RegisteredOwner>NA</RegisteredOwner>
      <DoNotCleanTaskBar>true</DoNotCleanTaskBar>
      <TimeZone>UTC</TimeZone>
    </component>
    <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Home_Page>about:blank</Home_Page>
      <DisableFirstRunWizard>true</DisableFirstRunWizard>
      <DisableOOBAccelerators>true</DisableOOBAccelerators>
      <DisableDevTools>true</DisableDevTools>
      <LocalIntranetSites>http://*.vm.net;https://*.vm.net</LocalIntranetSites>
      <TrustedSites>https://*.vm.net</TrustedSites>
    </component>
    <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <UserAuthentication>0</UserAuthentication>
    </component>
    <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <RunSynchronous>
        <RunSynchronousCommand wcm:action="add">
          <Description>EnableAdmin</Description>
          <Order>1</Order>
          <Path>cmd /c net user Administrator /active:yes</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
          <Description>UnfilterAdministratorToken</Description>
          <Order>2</Order>
          <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v FilterAdministratorToken /t REG_DWORD /d 0 /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
          <Description>Remove First Logon Animation</Description>
          <Order>3</Order>
	      <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v EnableFirstLogonAnimation /d 0 /t REG_DWORD /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
          <Description>Do Not Open Server Manager At Logon</Description>
          <Order>4</Order>
	      <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\ServerManager" /v "DoNotOpenServerManagerAtLogon" /d 1 /t REG_DWORD /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Do not Open Initial Configuration Tasks At Logon</Description>
            <Order>5</Order>
            <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\ServerManager\oobe" /v "DoNotOpenInitialConfigurationTasksAtLogon" /d 1 /t REG_DWORD /f</Path>
        </RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
			<Description>Set Power Scheme to High Performance</Description>
			<Order>6</Order>
			<Path>cmd /c powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c</Path>
		</RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
			<Description>Don't require password when console wakes up</Description>
			<Order>7</Order>
			<Path>cmd /c powercfg -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c fea3413e-7e05-4911-9a71-700331f1c294 0e796bdb-100d-47d6-a2d5-f7d2daa51f51 0</Path>
		</RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
			<Description>Sleep timeout</Description>
			<Order>8</Order>
			<Path>cmd /c powercfg -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 238c9fa8-0aad-41ed-83f4-97be242c8f20 29f6c1db-86da-48c5-9fdb-f2b67b1f44da 0</Path>
		</RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
			<Description>monitor timeout</Description>
			<Order>9</Order>
			<Path>cmd /c powercfg -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0</Path>
		</RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable PowerShell Remoting 1</Description>
            <Order>10</Order>
            <Path>cmd /c winrm quickconfig -quiet</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable PowerShell Remoting 2</Description>
            <Order>11</Order>
            <Path>cmd /c winrm quickconfig -quiet -force</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable PowerShell Remoting 3</Description>
            <Order>12</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell /v ExecutionPolicy /t REG_SZ /d Unrestricted /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Disable UAC</Description>
            <Order>13</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system /v EnableLUA /t REG_DWORD /d 0 /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Configure BgInfo to start automatically</Description>
            <Order>14</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v BgInfo /t REG_SZ /d "C:\Windows\BgInfo.exe C:\Windows\BgInfo.bgi /Timer:0 /nolicprompt" /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable Remote Desktop firewall rules</Description>
            <Order>15</Order>
            <Path>cmd /c netsh advfirewall Firewall set rule group="Remote Desktop" new enable=yes</Path>
        </RunSynchronousCommand>
      </RunSynchronous>
    </component>
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <InputLocale>0409:00000409</InputLocale>
      <SystemLocale>EN-US</SystemLocale>
      <UILanguage>EN-US</UILanguage>
      <UserLocale>EN-US</UserLocale>
    </component>
    <component name="Microsoft-Windows-TapiSetup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <TapiConfigured>0</TapiConfigured>
      <TapiUnattendLocation>
        <AreaCode>""</AreaCode>
        <CountryOrRegion>1</CountryOrRegion>
        <LongDistanceAccess>9</LongDistanceAccess>
        <OutsideAccess>9</OutsideAccess>
        <PulseOrToneDialing>1</PulseOrToneDialing>
        <DisableCallWaiting>""</DisableCallWaiting>
        <InternationalCarrierCode>""</InternationalCarrierCode>
        <LongDistanceCarrierCode>""</LongDistanceCarrierCode>
        <Name>Default</Name>
      </TapiUnattendLocation>
    </component>
    <component name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <IEHardenAdmin>false</IEHardenAdmin>
      <IEHardenUser>false</IEHardenUser>
    </component>
    <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <fDenyTSConnections>false</fDenyTSConnections>
    </component>
    <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <component name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <component name="Microsoft-Windows-NetBT" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
  </settings>
  <settings pass="oobeSystem">
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <FirstLogonCommands>
        <SynchronousCommand wcm:action="add">
            <CommandLine>winrm quickconfig -quiet</CommandLine>
            <Description>Enable Windows Remoting</Description>
            <Order>1</Order>
        </SynchronousCommand>
        <SynchronousCommand wcm:action="add">
            <CommandLine>winrm quickconfig -quiet -force</CommandLine>
            <Description>Enable Windows Remoting</Description>
            <Order>2</Order>
        </SynchronousCommand>
        <SynchronousCommand wcm:action="add">
            <CommandLine>winrm set winrm/config/service/auth @{CredSSP="true"}</CommandLine>
            <Description>Enable Windows Remoting CredSSP</Description>
            <Order>3</Order>
        </SynchronousCommand>
      </FirstLogonCommands>
      <UserAccounts>
        <AdministratorPassword>
          <Value>Password1</Value>
          <PlainText>true</PlainText>
        </AdministratorPassword>
        <LocalAccounts>
          <LocalAccount wcm:action="add">
            <Password>
              <Value>Password1</Value>
              <PlainText>true</PlainText>
            </Password>
            <Group>Administrators</Group>
            <DisplayName>AL</DisplayName>
            <Name>AL</Name>
          </LocalAccount>
        </LocalAccounts>
      </UserAccounts>
      <OOBE>
        <HideEULAPage>true</HideEULAPage>
        <NetworkLocation>Work</NetworkLocation>
        <ProtectYourPC>3</ProtectYourPC>
        <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
        <HideLocalAccountScreen>true</HideLocalAccountScreen>
      </OOBE>
      <RegisteredOrganization>vm.net</RegisteredOrganization>
      <RegisteredOwner>NA</RegisteredOwner>
    </component>
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <InputLocale>0409:00000409</InputLocale>
      <SystemLocale>En-US</SystemLocale>
      <UILanguage>EN-US</UILanguage>
      <UserLocale>EN-Us</UserLocale>
    </component>
  </settings>
</unattend>
'@

$unattendedXmlDefaultContent2008 = @'
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
  <settings pass="generalize">
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <DoNotCleanTaskBar>true</DoNotCleanTaskBar>
    </component>
    <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <SkipRearm>1</SkipRearm>
    </component>
  </settings>
  <settings pass="specialize">
    <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <Identification>
        <JoinWorkgroup xmlns="">NET</JoinWorkgroup>
      </Identification>
    </component>
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <ComputerName>SERVER</ComputerName>
      <RegisteredOrganization>vm.net</RegisteredOrganization>
      <RegisteredOwner>NA</RegisteredOwner>
      <DoNotCleanTaskBar>true</DoNotCleanTaskBar>
      <TimeZone>UTC</TimeZone>
    </component>
    <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Home_Page>about:blank</Home_Page>
      <DisableFirstRunWizard>true</DisableFirstRunWizard>
      <DisableOOBAccelerators>true</DisableOOBAccelerators>
      <DisableDevTools>true</DisableDevTools>
      <LocalIntranetSites>http://*.vm.net;https://*.vm.net</LocalIntranetSites>
      <TrustedSites>https://*.vm.net</TrustedSites>
    </component>
    <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <UserAuthentication>0</UserAuthentication>
    </component>
    <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <RunSynchronous>
        <RunSynchronousCommand wcm:action="add">
            <Description>EnableAdmin</Description>
            <Order>1</Order>
            <Path>cmd /c net user Administrator /active:yes</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>UnfilterAdministratorToken</Description>
            <Order>2</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v FilterAdministratorToken /t REG_DWORD /d 0 /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Remove First Logon Animation</Description>
            <Order>3</Order>
            <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v EnableFirstLogonAnimation /d 0 /t REG_DWORD /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Do Not Open Server Manager At Logon</Description>
            <Order>4</Order>
            <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\ServerManager" /v "DoNotOpenServerManagerAtLogon" /d 1 /t REG_DWORD /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Do not Open Initial Configuration Tasks At Logon</Description>
            <Order>5</Order>
            <Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\ServerManager\oobe" /v "DoNotOpenInitialConfigurationTasksAtLogon" /d 1 /t REG_DWORD /f</Path>
        </RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
            <Description>Set Power Scheme to High Performance</Description>
            <Order>6</Order>
            <Path>cmd /c powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c</Path>
		</RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
            <Description>Don't require password when console wakes up</Description>
			<Order>7</Order>
			<Path>cmd /c powercfg -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c fea3413e-7e05-4911-9a71-700331f1c294 0e796bdb-100d-47d6-a2d5-f7d2daa51f51 0</Path>
		</RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
			<Description>Sleep timeout</Description>
			<Order>8</Order>
			<Path>cmd /c powercfg -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 238c9fa8-0aad-41ed-83f4-97be242c8f20 29f6c1db-86da-48c5-9fdb-f2b67b1f44da 0</Path>
		</RunSynchronousCommand>
		<RunSynchronousCommand wcm:action="add">   
			<Description>monitor timeout</Description>
			<Order>9</Order>
			<Path>cmd /c powercfg -setacvalueindex 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c 7516b95f-f776-4464-8c53-06167f40cc99 3c0bc021-c8a8-4e07-a973-6b14cbcb2b7e 0</Path>
		</RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable PowerShell Remoting 1</Description>
            <Order>10</Order>
            <Path>cmd /c winrm quickconfig -quiet</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable PowerShell Remoting 2</Description>
            <Order>11</Order>
            <Path>cmd /c winrm quickconfig -quiet -force</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable PowerShell Remoting 2</Description>
            <Order>12</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell /v ExecutionPolicy /t REG_SZ /d Unrestricted /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Disable UAC</Description>
            <Order>13</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system /v EnableLUA /t REG_DWORD /d 0 /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Configure BgInfo to start automatically</Description>
            <Order>14</Order>
            <Path>cmd /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v BgInfo /t REG_SZ /d "C:\Windows\BgInfo.exe C:\Windows\BgInfo.bgi /Timer:0 /nolicprompt" /f</Path>
        </RunSynchronousCommand>
        <RunSynchronousCommand wcm:action="add">
            <Description>Enable Remote Desktop firewall rules</Description>
            <Order>15</Order>
            <Path>cmd /c netsh advfirewall Firewall set rule group="Remote Desktop" new enable=yes</Path>
        </RunSynchronousCommand>
      </RunSynchronous>
    </component>
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <InputLocale>0409:00000409</InputLocale>
      <SystemLocale>EN-US</SystemLocale>
      <UILanguage>EN-US</UILanguage>
      <UserLocale>EN-US</UserLocale>
    </component>
    <component name="Microsoft-Windows-TapiSetup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <TapiConfigured>0</TapiConfigured>
      <TapiUnattendLocation>
        <AreaCode>""</AreaCode>
        <CountryOrRegion>1</CountryOrRegion>
        <LongDistanceAccess>9</LongDistanceAccess>
        <OutsideAccess>9</OutsideAccess>
        <PulseOrToneDialing>1</PulseOrToneDialing>
        <DisableCallWaiting>""</DisableCallWaiting>
        <InternationalCarrierCode>""</InternationalCarrierCode>
        <LongDistanceCarrierCode>""</LongDistanceCarrierCode>
        <Name>Default</Name>
      </TapiUnattendLocation>
    </component>
    <component name="Microsoft-Windows-IE-ESC" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <IEHardenAdmin>false</IEHardenAdmin>
      <IEHardenUser>false</IEHardenUser>
    </component>
    <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <fDenyTSConnections>false</fDenyTSConnections>
    </component>
    <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <component name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <component name="Microsoft-Windows-NetBT" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
  </settings>
  <settings pass="oobeSystem">
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
      <FirstLogonCommands>
        <SynchronousCommand wcm:action="add">
            <CommandLine>winrm quickconfig -quiet</CommandLine>
            <Description>Enable Windows Remoting</Description>
            <Order>1</Order>
        </SynchronousCommand>
        <SynchronousCommand wcm:action="add">
            <CommandLine>winrm quickconfig -quiet -force</CommandLine>
            <Description>Enable Windows Remoting</Description>
            <Order>2</Order>
        </SynchronousCommand>
        <SynchronousCommand wcm:action="add">
            <CommandLine>winrm set winrm/config/service/auth @{CredSSP="true"}</CommandLine>
            <Description>Enable Windows Remoting CredSSP</Description>
            <Order>3</Order>
        </SynchronousCommand>
      </FirstLogonCommands>
      <UserAccounts>
        <AdministratorPassword>
          <Value>Password1</Value>
          <PlainText>true</PlainText>
        </AdministratorPassword>
        <LocalAccounts>
          <LocalAccount wcm:action="add">
            <Password>
              <Value>Password1</Value>
              <PlainText>true</PlainText>
            </Password>
            <Group>Administrators</Group>
            <DisplayName>AL</DisplayName>
            <Name>AL</Name>
          </LocalAccount>
        </LocalAccounts>
      </UserAccounts>
      <OOBE>
        <HideEULAPage>true</HideEULAPage>
		<NetworkLocation>Work</NetworkLocation>
        <ProtectYourPC>3</ProtectYourPC>
      </OOBE>
      <RegisteredOrganization>vm.net</RegisteredOrganization>
      <RegisteredOwner>NA</RegisteredOwner>
    </component>
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <InputLocale>0409:00000409</InputLocale>
      <SystemLocale>En-US</SystemLocale>
      <UILanguage>EN-US</UILanguage>
      <UserLocale>EN-Us</UserLocale>
    </component>
  </settings>
</unattend>
'@

#region Get-Type (helper function for creating generic types)
function Get-Type
{
    param (
        [Parameter(Position = 0, Mandatory = $true)]
        [string] $GenericType,
		
        [Parameter(Position = 1, Mandatory = $true)]
        [string[]] $T
    )
	
    $T = $T -as [type[]]
	
    try
    {
        $generic = [type]($GenericType + '`' + $T.Count)
        $generic.MakeGenericType($T)
    }
    catch [Exception]
    {
        throw New-Object -TypeName System.Exception -ArgumentList ('Cannot create generic type', $_.Exception)
    }
}
#endregion
#region Invoke-Ternary
function Invoke-Ternary 
{
    param
    (
        [scriptblock]
        $decider,

        [scriptblock]
        $ifTrue,

        [scriptblock]
        $ifFalse
    )

    if (&$decider)
    {
        &$ifTrue
    }
    else
    {
        &$ifFalse
    }
}
Set-Alias -Name ?? -Value Invoke-Ternary -Option AllScope -Description "Ternary Operator like '?' in C#"
#endregion
#endregion

#region Lab Definition Functions
#region New-LabDefinition
function New-LabDefinition
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]$Name,
		
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$Path,
		
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$VmPath,
		
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [int]$ReferenceDiskSizeInGB = 50,
		
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [int]$MaxMemory = 0,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [switch]$UseAllMemory = $false,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [switch]$UseStaticMemory = $false,

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Azure', 'HyperV', 'VMware')]
        [string]$DefaultVirtualizationEngine,

        [switch]$NoAzurePublishSettingsFile,

        [string]$AzureSubscriptionName
    )

    Write-LogFunctionEntry

    $hostOSVersion = [System.Version](Get-CimInstance -ClassName Win32_OperatingSystem).Version 
    if (($hostOSVersion -lt [System.Version]'6.2') -or (($hostOSVersion -ge [System.Version]'6.4') -and ($hostOSVersion -lt [System.Version]'10.0.10122')))
    {
        Write-Host '***************************************************************************' 
        Write-Host '*                                                                         *' 
        Write-Host '*     THIS HOST MACHINE IS NOT RUNNING AN OS SUPPORTED BY AUTOMATEDLAB!   *'
        Write-Host '*                                                                         *' 
        Write-Host '*   Operating System detected as:                                         *'
        Write-Host "*     Name:  $((Get-CimInstance -ClassName Win32_OperatingSystem).Caption.PadRight(60)) *" 
        Write-Host "*     Build: $((Get-CimInstance -ClassName Win32_OperatingSystem).Version.PadRight(60)) *" 
        Write-Host '*                                                                         *' 
        Write-Host '*                                                                         *' 
        Write-Host '* AutomatedLab is supported on the following virtualization platforms     *' 
        Write-Host '*                                                                         *' 
        Write-Host '* - Microsoft Azure                                                       *' 
        Write-Host '* - Windows 10 build 10.0.10122 or newer                                  *' 
        Write-Host '* - Windows 8.0 Professional                                              *' 
        Write-Host '* - Windows 8.0 Enterprise                                                *' 
        Write-Host '* - Windows 8.1 Professional                                              *' 
        Write-Host '* - Windows 8.1 Enterprise                                                *' 
        Write-Host '* - Windows 2012 Server Standard                                          *' 
        Write-Host '* - Windows 2012 Server DataCenter                                        *' 
        Write-Host '* - Windows 2012 R2 Server Standard                                       *' 
        Write-Host '* - Windows 2012 R2 Server DataCenter                                     *' 
        Write-Host '*                                                                         *' 
        Write-Host '***************************************************************************'
    }
    
    $Global:scriptStart = Get-Date
    $Global:taskStart = @()
    $Global:indent = 0
    
    $Global:labDeploymentNoNewLine = $false
    
    Write-ScreenInfo -Message 'Initialization' -TimeDelta ([timespan]0) -TimeDelta2 ([timespan]0) -TaskStart
    Write-ScreenInfo -Message "Current time: $((Get-Date).ToString())"
    Write-ScreenInfo -Message "Host operating system version: $($hostOSVersion.ToString())"    
	
    Write-ScreenInfo -Message 'Creating a new lab definition'
    
    if (-not $Name)
    {
        $reservedMacAddresses = @()

        #Microsoft
        $reservedMacAddresses += '00:03:FF'
        $reservedMacAddresses += '00:0D:3A'
        $reservedMacAddresses += '00:12:5A'
        $reservedMacAddresses += '00:15:5D'
        $reservedMacAddresses += '00:17:FA'
        $reservedMacAddresses += '00:50:F2'
        $reservedMacAddresses += '00:1D:D8'

        #VMware
        $reservedMacAddresses += '00:05:69'
        $reservedMacAddresses += '00:0C:29'
        $reservedMacAddresses += '00:1C:14'
        $reservedMacAddresses += '00:50:56'
 
        #Citrix
        $reservedMacAddresses += '00:16:3E'

        $macAddress = Get-WmiObject Win32_NetworkAdapter | 
        Where-Object { $_.NetEnabled -and $_.NetConnectionID } | 
        Where-Object { $_.MACaddress.ToString().SubString(0, 8) -notin $reservedMacAddresses } |
        Select-Object -ExpandProperty MACAddress -Unique
        
        $Name = "$($env:COMPUTERNAME)$($macAddress.SubString(12,2))$($macAddress.SubString(15,2))"
        Write-ScreenInfo -Message "Lab name and network name has automatically been generated as '$Name' (if not overridden)"
    }
    else
    {
        Write-ScreenInfo -Message "Lab name: $Name"
    }

    #remove the current lab from memory
    if (Get-Lab -ErrorAction SilentlyContinue) { Clear-Lab }

    $Global:labExported = $false
    
    $Global:firstMachineIsStarted = $false
    $Global:firstAzureVMCreated = $false
    
    $global:cacheVMs = $null

    #cleanup $PSDefaultParameterValues for entries for AL functions
    $automatedLabPSDefaultParameterValues = $global:PSDefaultParameterValues.GetEnumerator() | Where-Object {(Get-Command ($_.Name).Split(':')[0]).Module -like 'Automated*'}
    if ($automatedLabPSDefaultParameterValues)
    {
        foreach ($entry in $automatedLabPSDefaultParameterValues)
        {
            $global:PSDefaultParameterValues.Remove($entry.Name)
            Write-ScreenInfo -Message "Entry '$($entry.Name)' with value '$($entry.Value)' was removed from `$PSDefaultParameterValues. If needed, modify `$PSDefaultParameterValues after calling New-LabDefinition'" -Type Warning
        }
    }

    if (Get-Variable -Name 'autoIPAddress' -Scope Script -ErrorAction SilentlyContinue)
    {
        Remove-Variable -Name 'AutoIPAddress' -Scope Script
    }
    
    if ($global:labNamePrefix) { $Name = "$global:labNamePrefix$Name" }
	
    if ($PSBoundParameters.ContainsKey('Path'))
    {
        $script:labPath = $Path
    }
    else
    {
        $script:labpath = "$([System.Environment]::GetFolderPath('MyDocuments'))\AutomatedLab-Labs\$Name"
    }
    Write-ScreenInfo -Message "Location for lab definition files will be '$($script:labpath)'"
    
    $script:defaults = $MyInvocation.MyCommand.Module.PrivateData
	
    $script:lab = New-Object AutomatedLab.Lab
	
    $script:lab.Name = $Name
	
    while (Get-LabVirtualNetworkDefinition)
    {
        Remove-LabVirtualNetworkDefinition -Name (Get-LabVirtualNetworkDefinition)[0].Name
    }
    
    $machineDefinitionFilePath = Join-Path -Path $script:labPath -ChildPath $defaults.MachineFileName
    $machineDefinitionFile = New-Object AutomatedLab.MachineDefinitionFile
    $machineDefinitionFile.Path = $machineDefinitionFilePath
    $script:lab.MachineDefinitionFiles.Add($machineDefinitionFile)
	
    $diskDefinitionFilePath = Join-Path -Path $script:labPath -ChildPath $defaults.DiskFileName
    $diskDefinitionFile = New-Object AutomatedLab.DiskDefinitionFile
    $diskDefinitionFile.Path = $diskDefinitionFilePath
    $script:lab.DiskDefinitionFiles.Add($diskDefinitionFile)
	
    Write-ScreenInfo -Message "LabSources folder detected at '$(Get-LabSourcesLocation)'"
    
    if (-not (Get-LabIsoImageDefinition) -and $DefaultVirtualizationEngine -ne 'Azure')
    {
        if (Get-ChildItem -Path "$(Get-LabSourcesLocation)\ISOs" -Filter *.iso -Recurse)
        {
            Write-ScreenInfo -Message 'Auto-adding ISO files'
            Add-LabIsoImageDefinition -Path "$(Get-LabSourcesLocation)\ISOs"
        }
        else
        {
            Write-ScreenInfo -Message "No ISO files found in $(Get-LabSourcesLocation)\ISOs folder. If using Hyper-V for lab machines, please add ISO files manually using 'Add-LabIsoImageDefinition'" -Type Warning
        }
    }    
    
    if ($DefaultVirtualizationEngine)
    {
        $script:lab.DefaultVirtualizationEngine = $DefaultVirtualizationEngine
    }    
    
    if ($MaxMemory -ne 0)
    {
        $script:lab.MaxMemory = $MaxMemory
    }
    if ($UseAllMemory)
    {
        $script:lab.MaxMemory = 4TB
    }

    $script:lab.UseStaticMemory = $UseStaticMemory

    $script:lab.Sources.UnattendedXml = $script:labPath
    if ($VmPath)
    {
        $Script:lab.target.Path = $vmPath
        Write-ScreenInfo -Message "Path for VMs specified as '$($script:lab.Target.Path)'" -Type Info
    }	
	
    $script:lab.Target.ReferenceDiskSizeInGB = $ReferenceDiskSizeInGB
	
    $type = Get-Type -GenericType AutomatedLab.ListXmlStore -T AutomatedLab.Machine
    $script:machines = New-Object $type
    $type = Get-Type -GenericType AutomatedLab.ListXmlStore -T AutomatedLab.Disk
    $script:disks = New-Object $type
	
    <#
            Removed as this breaks the current design. This goes to a further version
            if ($DefaultVirtualizationEngine -eq 'Azure')
            {
            $publishSettingsFile = Get-ChildItem -Path "$(Get-LabSourcesLocation)" -Filter *.publishsettings -Recurse
            if ($publishSettingsFile)
            {
            $useFile = ($publishSettingsFile | Sort-Object -Property LastWriteTime | Select-Object -Last 1).FullName
            Write-ScreenInfo -Message "Automatically using Azure publish settings file '$useFile'"
            if ($AzureSubscriptionName)
            {
            Add-LabAzureSubscription -Path $useFile -SubscriptionName $AzureSubscriptionName
            }
            else
            {
            Add-LabAzureSubscription -Path $useFile
            }
            }
            }
    #>
    
    Write-LogFunctionExit
}
#endregion New-LabDefinition

#region Get-LabDefinition
function Get-LabDefinition
{
    [CmdletBinding()]
    [OutputType([AutomatedLab.Lab])]
    param ()
	
    Write-LogFunctionEntry
	
    return $script:lab
	
    Write-LogFunctionExit
}
#endregion Get-LabDefinition

#region Export-LabDefinition
function Export-LabDefinition
{
    [CmdletBinding()]
    param (
        [switch]$Force,
                         
        [switch]$ExportDefaultUnattendedXml = $true
    )
            
    Write-LogFunctionEntry
            
    if (Get-LabMachineDefinition | Where-Object HostType -eq 'HyperV')
    {
        $osesCount = (Get-LabAvailableOperatingSystem).Count
        Write-ScreenInfo -Message "Unique number of operating systems available for Hyper-V in this lab: $osesCount"
    }
    
    #Automatic DNS configuration in Azure if no DNS server is specified and an AD is being deployed
    foreach ($network in (Get-LabVirtualNetworkDefinition))
    {
        if ($network.HostType -eq 'Azure' -and (Get-LabMachineDefinition -Role RootDC))
        {
            $rootDCs = Get-LabMachineDefinition -Role RootDC
            $dnsServerIP = ''
            if ($rootDCs | Where-Object {$_.Network -eq $network})
            {
                $dnsServerIP = ($rootDCs)[0].IpV4Address
            }
            elseif ($rootDCs | Where-Object {$_.Network -eq $network})
            {
                $dnsServerIP = ($rootDCs | Where-Object {$_.Network -eq $network})[0].IpV4Address
            }
            if ($dnsServerIP)
            {
                (Get-LabVirtualNetworkDefinition)[0].DnsServers = $dnsServerIP 
                $dnsServerName = (Get-LabMachineDefinition | Where-Object {$_.IpV4Address -eq $dnsServerIP}).Name
                Write-ScreenInfo -Message "No DNS server was defined for Azure virtual network while AD is being deployed. Setting DNS server to IP address of '$dnsServerName'" -Type Warning
            }
        }
    }
    
    #Automatic DNS (client) configuration of machines
    $firstRoot = Get-LabMachineDefinition -Role RootDC | Select-Object -First 1
    
    if ($firstRoot)
    {
        foreach ($machine in (Get-LabMachineDefinition | Where-Object HostType -ne Azure))
        {
            if ($machine.NetworkAdapters[0].IPv4DnsServers.AddressAsString -eq '0.0.0.0')
            {
                if (-not $machine.IsDomainJoined)
                {
                    $machine.NetworkAdapters[0].IPv4DnsServers = $firstRoot.NetworkAdapters[0].Ipv4Address[0].IpAddress
                }
                elseif ($machine.Roles.Name -contains 'RootDC')
                {
                    $machine.NetworkAdapters[0].IPv4DnsServers = $machine.NetworkAdapters[0].Ipv4Address[0].IpAddress
                }
                elseif ($machine.Roles.Name -contains 'FirstChildDC')
                {
                    $machine.NetworkAdapters[0].IPv4DnsServers = $firstRoot.NetworkAdapters[0].Ipv4Address[0].IpAddress
                }
                else
                {
                    $rootDc = Get-LabMachineDefinition -Role RootDC | Where-Object DomainName -eq $machine.DomainName
                    if ($rootDc)
                    {
                        $machine.NetworkAdapters[0].IPv4DnsServers = $rootDc.NetworkAdapters[0].Ipv4Address[0].IpAddress
                    }
                    else
                    {
                        $machine.NetworkAdapters[0].IPv4DnsServers = (Get-LabMachineDefinition -Role FirstChildDC | Where-Object DomainName -eq $machine.DomainName).NetworkAdapters[0].Ipv4Address[0].IpAddress
                    }
                }
            }
        }
    }
    
    if (Get-LabMachineDefinition | Where-Object HostType -eq HyperV)
    {
        $hypervMachines = Get-LabMachineDefinition | Where-Object HostType -eq HyperV
        $hypervUsedOperatingSystems = Get-LabAvailableOperatingSystem | Where-Object OperatingSystemImageName -in $hypervMachines.OperatingSystem.OperatingSystemName

        $spaceNeededBaseDisks = ($hypervUsedOperatingSystems | Measure-Object -Property Size -Sum).Sum
        $spaceBaseDisksAlreadyClaimed = ($hypervUsedOperatingSystems | Measure-Object -Property size -Sum).Sum
        $spaceNeededData = ($hypervMachines | Where-Object { -not (Get-VM -Name $_.Name -ErrorAction SilentlyContinue) }).Count * 2GB
        
        $spaceNeeded = $spaceNeededBaseDisks + $spaceNeededData - $spaceBaseDisksAlreadyClaimed
        
        Write-Verbose -Message "Space needed by HyperV base disks:                     $([int]($spaceNeededBaseDisks / 1GB))"
        Write-Verbose -Message "Space needed by HyperV base disks but already claimed: $([int]($spaceBaseDisksAlreadyClaimed / 1GB * -1))"
        Write-Verbose -Message "Space estimated for HyperV data:                       $([int]($spaceNeededData / 1GB))"
        Write-ScreenInfo -Message "Estimated (additional) local drive space needed in total for machines: $([System.Math]::Round(($spaceNeeded / 1GB),2)) GB" -Type Info
        
        if ((Get-LabDefinition).target.Path)
        {
            $freeSpace = (Get-PSDrive -Name ((Get-LabDefinition).target.Path)[0]).Free
            if ($freeSpace -lt $spaceNeeded)
            {
                Throw "VMpath parameter is specified for the lab and contains: '$((Get-LabDefinition).Target.Path)'. However, estimated needed space be $([int]($spaceNeeded / 1GB))GB but drive has only $([System.Math]::Round($freeSpace / 1GB)) GB of free space"
            }            
        }
        else
        {
            if (-not (Set-LabLocalVirtualMachineDiskAuto -SpaceNeeded $spaceNeeded) -or (-not $script:lab.Target.Path))
            {
                Throw 'No local drive found matching requirements for free space'
            }
        }
        Write-ScreenInfo -Message "Location for Hyper-V machines will be: $((Get-LabDefinition).Target.Path)"
        
        if (-not (Test-Path ((Get-LabDefinition).Target.Path)))
        {
            New-Item -ItemType Directory -Path ((Get-LabDefinition).Target.Path) | Out-Null
        }

    }
    
    
    $lab.LabFilePath = Join-Path -Path $script:labPath -ChildPath $script:defaults.LabFileName
    $script:lab | Add-Member -Name Path -MemberType NoteProperty -Value $labFilePath -Force
    
    if (-not (Test-Path $script:labPath))
    {
        New-Item -Path $script:labPath -ItemType Directory | Out-Null
    }
    
    if (Test-Path -Path $lab.LabFilePath)
    {
        if ($Force)
        {
            Remove-Item -Path $lab.LabFilePath
        }
        else
        {
            Write-Error 'The file does already exist' -TargetObject $lab.LabFilePath
            return
        }
    }
            
    try
    {
        $script:lab.Export($lab.LabFilePath)
    }
    catch
    {
        throw $_
    }
            
    $machineFilePath = $script:lab.MachineDefinitionFiles[0].Path
    $diskFilePath = $script:lab.DiskDefinitionFiles[0].Path
            
    if (Test-Path -Path $machineFilePath)
    {
        if ($Force)
        {
            Remove-Item -Path $machineFilePath
        }
        else
        {
            Write-Error 'The file does already exist' -TargetObject $machineFilePath
            return
        }
    }

    $script:machines.Export($machineFilePath)
    $script:disks.Export($diskFilePath)
            
    if ($ExportDefaultUnattendedXml)
    {
        if ($script:machines.Count -eq 0)
        {
            Write-Warning 'There are no machines defined, nothing to export'
            return
        }
            
        if ($Script:machines.OperatingSystem | Where-Object Version -lt '6.2')
        {
            $unattendedXmlDefaultContent2008 | Out-File -FilePath (Join-Path -Path $script:lab.Sources.UnattendedXml.Value -ChildPath Unattended2008.xml) -Encoding unicode
        }
        if ($Script:machines.OperatingSystem | Where-Object Version -ge '6.2')
        {
            $unattendedXmlDefaultContent2012 | Out-File -FilePath (Join-Path -Path $script:lab.Sources.UnattendedXml.Value -ChildPath Unattended2012.xml) -Encoding unicode
        }
    }
            
    $Global:labExported = $true
    
    Write-LogFunctionExit
}
#endregion Export-LabDefinition

#region Test-LabDefinition
function Test-LabDefinition
{
    [CmdletBinding()]
    param (
        [string]$Path,
		
        [switch]$Quiet
    )
	
    Write-LogFunctionEntry

    $script:defaults = $MyInvocation.MyCommand.Module.PrivateData

    $lab = Get-LabDefinition
    if (-not $lab)
    {
        $lab = Get-Lab -ErrorAction SilentlyContinue
    }

    if (-not $lab -and -not $Path)
    {
        Write-Error 'There is no lab loaded and no path specified. Please either import a lab using Import-Lab or point to a lab.xml document using the path parameter'
        return $false
    }
	
    if (-not $Path)
    {
        $Path = Join-Path -Path $lab.LabPath -ChildPath $script:defaults.LabFileName
    }
	
    #we need to get the machine config files as well
    try
    {
        $machineDefinitionFiles = ([xml](Get-Content -Path $Path) | Select-Xml -XPath '//MachineDefinitionFile' -ErrorAction Stop).Node.Path
    }
    catch
    {
        Write-Error -Message 'Cannot read lab file'
        return $false
    }
	
    Write-Verbose "There are $($machineDefinitionFiles.Count) machine XML file referenced in the lab xml file"
    foreach ($machineDefinitionFile in $machineDefinitionFiles)
    {
        if (-not (Test-Path -Path $machineDefinitionFile))
        {
            throw 'Error importing the machines. Verify the paths in the section <MachineDefinitionFiles> of the lab definition XML file.'
        }
    }
    
    $Script:ValidationPass = $true
	
    Write-Verbose 'Starting validation against all xml files'
    try
    {
        [AutomatedLab.XmlValidatorArgs]::XmlPath = $Path
        
        $summaryMessageContainer = New-Object AutomatedLab.ValidationMessageContainer
        
        $assembly = [System.Reflection.Assembly]::GetAssembly([AutomatedLab.ValidatorBase])
        
        $validatorCount = 0
        foreach ($t in $assembly.GetTypes())
        {
            if ($t.IsSubclassOf([AutomatedLab.ValidatorBase]))
            {
                $validator = [AutomatedLab.ValidatorBase][System.Activator]::CreateInstance($t)
                Write-Debug "Validator '$($validator.MessageContainer.ValidatorName)' took $($validator.Runtime.TotalMilliseconds) milliseconds"
                
                $summaryMessageContainer += $validator.MessageContainer
                $validatorCount++
            }
        }
        
        $summaryMessageContainer.AddSummary()
    }
    catch
    {
        throw $_
    }
    
    Write-Verbose -Message "Lab Validation complete, overvall runtime was $($summaryMessageContainer.Runtime)"
    
    $messages = $summaryMessageContainer | ForEach-Object { $_.GetFilteredMessages('All') }
    if (-not $Quiet)
    {
        Write-ScreenInfo ($messages | Where-Object { $_.Type -band [AutomatedLab.MessageType]::Default } | Out-String)

        if ($VerbosePreference -eq 'Continue')
        {
            Write-Verbose ($messages | Where-Object { $_.Type -band [AutomatedLab.MessageType]::VerboseDebug } | Out-String)
        }
    }
    else
    {
        if ($messages | Where-Object { $_.Type -band [AutomatedLab.MessageType]::Warning })
        {
            $messages | Where-Object { $_.Type -band [AutomatedLab.MessageType]::Warning } | ForEach-Object `
            {
                Write-Screeninfo -Message "Issue: '$($_.TargetObject)'. Cause: $($_.Message)" -Type Warning
            }
        }
        
        if ($messages | Where-Object { $_.Type -band [AutomatedLab.MessageType]::Error })
        {
            $messages | Where-Object { $_.Type -band [AutomatedLab.MessageType]::Error } | ForEach-Object `
            {
                Write-Screeninfo -Message "Issue: '$($_.TargetObject)'. Cause: $($_.Message)" -Type Error
            }
        }
    }
	
    if ($messages | Where-Object Type -eq ([AutomatedLab.MessageType]::Error))
    {		
        $Script:ValidationPass = $false
        $false
    }
    else
    {
        Write-ScreenInfo 'Validation passed.'
        $Script:ValidationPass = $true
        $true
    }
	
    Write-LogFunctionExit
}
#endregion Test-LabDefinition
#endregion Lab Definition Functions

#region Domain Definition Functions
#region Add-LabDomainDefinition
function Add-LabDomainDefinition
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Name,
		
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$AdminUser,
		
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$AdminPassword,
		
        [switch]$PassThru
    )
	
    Write-LogFunctionEntry
	
    if ($script:lab.Domains | Where-Object { $_.Name -eq $Name })
    {
        $errorMessage = "A domain with the name '$Name' is already defined"
        Write-Error $errorMessage
        Write-LogFunctionExitWithError -Message $errorMessage
        return
    }
	
    $domain = New-Object -TypeName AutomatedLab.Domain
    $domain.Name = $Name
	
    $user = New-Object -TypeName AutomatedLab.User
    $user.UserName = $AdminUser
    $user.Password = $AdminPassword
	
    $domain.Administrator = $user
	
    $script:lab.Domains.Add($domain)
    Write-Verbose "Added domain '$Name'. Lab now has $($Script:lab.Domains.Count) domain(s) defined"
	
    if ($PassThru)
    {
        $network
    }
	
    Write-LogFunctionExit
}
#endregion Add-LabDomainDefinition

#region Get-LabDomainDefinition
function Get-LabDomainDefinition
{
    Write-LogFunctionEntry
	
    return $script:lab.Domains
	
    Write-LogFunctionExit
}
#endregion Get-LabDomainDefinition

#region Remove-LabDomainDefinition
function Remove-LabDomainDefinition
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$Name
    )
	
    Write-LogFunctionEntry
	
    $domain = $script:lab.Domains | Where-Object { $_.Name -eq $Name }
	
    if (-not $domain)
    {
        Write-Warning "There is no domain defined with the name '$Name'"
    }
    else
    {
        [Void]$script:lab.Domains.Remove($domain)
        Write-Verbose "Domain '$Name' removed. Lab has $($Script:lab.Domains.Count) domain(s) defined"
    }
	
    Write-LogFunctionExit
}
#endregion Remove-LabDomainDefinition
#endregion Domain Definition Functions

#region Iso Image Definition Functions
#region Add-LabIsoImageDefinition
function Add-LabIsoImageDefinition
{
    [CmdletBinding()]
    param (
        
        [string]$Name,
		
        [string]$Path,

        [Switch]$IsOperatingSystem,

        [switch]$NoDisplay
    )
	
    Write-LogFunctionEntry
    
    if ($IsOperatingSystem)
    {
        Write-Warning -Message 'The -IsOperatingSystem switch parameter is obsolete and thereby ignored'
    }
    
    if (-not $script:lab)
    {
        throw 'Please create a lab before using this cmdlet. To create a new lab, call New-LabDefinition'
    }
    
    
    $type = Get-Type -GenericType AutomatedLab.ListXmlStore -T AutomatedLab.IsoImage
    #read the cache
    try
    {
        $importMethodInfo = $type.GetMethod('ImportFromRegistry', [System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::Static)
        $cachedIsos = $importMethodInfo.Invoke($null, ('Cache', 'LocalIsoImages'))
        Write-Verbose "Read $($cachedIsos.Count) ISO images from the cache"
    }
    catch
    {
        Write-Verbose 'Could not read ISO images info from the cache'
        $cachedIsos = New-Object $type
    }

    $isoFiles = Get-ChildItem -Path $Path -Filter *.iso -Recurse -ErrorAction SilentlyContinue
    if (-not $isoFiles)
    {
        throw "The specified iso file could not be found or no ISO file could be found in the given folder: $Path"
    }

    $isos = @()
    foreach ($isoFile in $isoFiles)
    {
        if (-not $PSBoundParameters.ContainsKey('Name'))
        {
            $Name = [guid]::NewGuid()
        }

        $iso = New-Object -TypeName AutomatedLab.IsoImage    
        $iso.Name = $Name
        $iso.Path = $isoFile.FullName
        $iso.Size = $isoFile.Length

        if ($cachedIsos -contains $iso)
        {
            Write-Verbose "The ISO '$($iso.Path)' with a size '$($iso.Size)' is already in the cache."
            $cachedIso = ($cachedIsos -eq $iso)[0]
            if ($PSBoundParameters.ContainsKey('Name'))
            {
                $cachedIso.Name = $Name
            }
            $isos += $cachedIso
        }
        else
        {
            Write-Verbose "The ISO '$($iso.Path)' with a size '$($iso.Size)' is not in the cache. Reading the operating systems from ISO."
            Mount-DiskImage -ImagePath $isoFile.FullName -StorageType ISO
            Get-PSDrive | Out-Null #This is just to refresh the drives. Somehow if this cmdlet is not called, PowerShell does not see the new drives.
            $letter = (Get-DiskImage -ImagePath $isoFile.FullName | Get-Volume).DriveLetter
            $isOperatingSystem = (Test-Path "$letter`:\Sources\Install.wim")
            DisMount-DiskImage -ImagePath $isoFile.FullName
            
            if ($isOperatingSystem)
            {
                $oses = Get-LabAvailableOperatingSystem -Path $isoFile.FullName
                if ($oses)
                {
                    foreach ($os in $oses)
                    {
                        if ($isos.OperatingSystems -contains $os)
                        {
                            throw "The operating system '$($os.OperatingSystemName)' with version '$($os.Version)' is already added to the lab. If this is an issue with cached information, use Clear-LabCache to solve the issue."
                        }
                        $iso.OperatingSystems.Add($os) | Out-Null
                    }
                }
                $cachedIsos.Add($iso) #the new ISO goes into the cache
                $isos += $iso
            }
            else
            {
                $cachedIsos.Add($iso) #ISO is not an OS. Add only if 'Name' is specified. Hence, ISO is manually added
                $isos += $iso
            }
        }
    }

    $duplicateOperatingSystems = $isos | Where-Object { $_.OperatingSystems } | 
    Group-Object -Property { "$($_.OperatingSystems.OperatingSystemName) $($_.OperatingSystems.Version)" } |
    Where-Object Count -gt 1

    if ($duplicateOperatingSystems)
    {
        $duplicateOperatingSystems.Group | 
        ForEach-Object { $_ } -PipelineVariable iso | 
        ForEach-Object { 
        ForEach-Object { $_.OperatingSystems } } |
        ForEach-Object { Write-Warning "The operating system $($_.OperatingSystemName) version $($_.Version) defined more than once in '$($iso.Path)'" }

        throw 'Duplicate operating systems detected. Check the previous warnings for details.'
    }

    $cachedIsos.ExportToRegistry('Cache', 'LocalIsoImages')
    
    foreach ($iso in $isos)
    {
        $script:lab.Sources.ISOs.Remove($iso) | Out-Null
        $script:lab.Sources.ISOs.Add($iso)
        if (-not $NoDisplay)
        {
            $osString = ''
            if ($iso.IsOperatingSystem) { $osString = ' (Operating System)' }
            Write-ScreenInfo -Message "Added ISO file '$($iso.Path)'$osString"
        }
    }
    Write-Verbose "Final Lab ISO count: $($script:lab.Sources.ISOs.Count)"
    
    Write-LogFunctionExit
}
#endregion Add-LabIsoImageDefinition

#region Get-LabIsoImageDefinition
function Get-LabIsoImageDefinition
{
    Write-LogFunctionEntry
	
    $script:lab.Sources.ISOs
	
    Write-LogFunctionExit
}
#endregion Get-LabIsoImageDefinition

#region Remove-LabIsoImageDefinition
function Remove-LabIsoImageDefinition
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Name
    )
	
    Write-LogFunctionEntry
	
    $iso = $script:lab.Sources.ISOs | Where-Object -FilterScript {
        $_.Name -eq $Name
    }
	
    if (-not $iso)
    {
        Write-Warning "There is no Iso Image defined with the name '$Name'"
    }
    else
    {
        [Void]$script:lab.Sources.ISOs.Remove($iso)
        Write-Verbose "Iso Image '$Name' removed. Lab has $($Script:lab.Sources.ISOs.Count) Iso Image(s) defined"
    }
	
    Write-LogFunctionExit
}
#endregion Remove-LabIsoImageDefinition
#endregion Iso Image Definition Functions

#region Machine Definition Functions
#region Add-LabDiskDefinition
function Add-LabDiskDefinition
{
    [CmdletBinding()]
    [OutputType([AutomatedLab.Disk])]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript({
                    $doesAlreadyExist = Test-Path -Path $_
                    if ($doesAlreadyExist)
                    {
                        Write-Warning 'The disk does already exist'
                        return $false
                    }
                    else
                    {
                        return $true
                    }
                }
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
		
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateRange(20, 1024)]
        [ValidateNotNullOrEmpty()]
        [int]$DiskSizeInGb = 60,
		
        [switch]$PassThru
    )
	
    Write-LogFunctionEntry
	
    if ($script:disks -eq $null)
    {
        $errorMessage = "Create a new lab first using 'New-LabDefinition' before adding disks"
        Write-Error $errorMessage
        Write-LogFunctionExitWithError -Message $errorMessage
        return
    }
	
    if ($Name)
    {
        if ($script:disks | Where-Object { $_.Name -eq $Name }
        )
        {
            $errorMessage = "A disk with the name '$Path ' does already exist"
            Write-Error $errorMessage
            Write-LogFunctionExitWithError -Message $errorMessage
            return
        }
    }
	
    $disk = New-Object -TypeName AutomatedLab.Disk
    $disk.Name = $Name
    $disk.DiskSize = $DiskSizeInGb
	
    $script:disks.Add($disk)
	
    Write-Verbose "Added disk '$Name' with path '$Path'. Lab now has $($Script:disks.Count) disk(s) defined"
	
    if ($PassThru)
    {
        $disk
    }
	
    Write-LogFunctionExit
}
#endregion Add-LabDiskDefinition

#region Add-LabMachineDefinition
function Add-LabMachineDefinition
{
    [CmdletBinding(DefaultParameterSetName = 'Network')]
    [OutputType([AutomatedLab.Machine])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern("^([\'\""a-zA-Z0-9-]){1,15}$")]
        [string]$Name,
		
        [ValidateRange(128MB, 128GB)]
        [double]$Memory,
		
        [ValidateRange(128MB, 128GB)]
        [double]$MaxMemory,
		
        [ValidateRange(128, 131072)]
        [int]$MemoryInMb,
		
        [ValidateRange(128, 131072)]
        [int]$MaxMemoryInMb,
		
        [ValidateRange(1, 64)]
        [ValidateNotNullOrEmpty()]
        [int]$Processors = 0,
		
        #Does not work with diff disks, the size of the reference disk matters
        [ValidateRange(20, 1024)]
        [ValidateNotNullOrEmpty()]
        [int]$DiskSizeInGb = 60,
		
        [ValidatePattern('^([a-zA-Z0-9-_]){2,15}$')]
        [string[]]$DiskName,
		
        [ValidateSet(
                'Windows 7 PROFESSIONAL', 'Windows 7 ULTIMATE', 'Windows 7 Enterprise',
                'Windows 8 Pro', 'Windows 8 Enterprise',
                'Windows 8.1 Pro', 'Windows 8.1 Enterprise',
                'Windows 10 Pro', 'Windows 10 Enterprise', 'Windows 10 Pro Technical Preview', 'Windows 10 Enterprise Technical Preview',
                'Windows Server 2008 R2 SERVERDATACENTER', 'Windows Server 2008 R2 SERVERDATACENTERCORE', 'Windows Server 2008 R2 SERVERENTERPRISE', 'Windows Server 2008 R2 SERVERENTERPRISECORE', 'Windows Server 2008 R2 SERVERSTANDARD', 'Windows Server 2008 R2 SERVERSTANDARDCORE', 'Windows Server 2008 R2 SERVERWEB', 'Windows Server 2008 R2 SERVERWEBCORE',
                'Windows Server 2012 SERVERDATACENTER', 'Windows Server 2012 SERVERDATACENTERCORE', 'Windows Server 2012 SERVERSTANDARD', 'Windows Server 2012 SERVERSTANDARDCORE',
                'Windows Server 2012 R2 SERVERDATACENTER', 'Windows Server 2012 R2 SERVERDATACENTERCORE', 'Windows Server 2012 R2 SERVERSTANDARD', 'Windows Server 2012 R2 SERVERSTANDARDCORE',
                'Windows Server 2016 Technical Preview 4 SERVERSTANDARDCORE', 'Windows Server 2016 Technical Preview 4 SERVERSTANDARD', 'Windows Server 2016 Technical Preview 4 SERVERDATACENTERCORE', 'Windows Server 2016 Technical Preview 4 SERVERDATACENTER'
        )]
        [Alias('OS')]
        [AutomatedLab.OperatingSystem]$OperatingSystem = (Get-LabDefinition).DefaultOperatingSystem,
		
        [string]$OperatingSystemVersion,
		
        [Parameter(ParameterSetName = 'Network')]
        [ValidatePattern('^([a-zA-Z0-9])|([ ]){2,244}$')]
        [string]$Network,

        [Parameter(ParameterSetName = 'Network')]
        [ValidatePattern('^(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9])[.]){3}(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9]))(\/[1-3]?[1-9])?$')]
        [string]$IpAddress,

        [Parameter(ParameterSetName = 'Network')]
        [ValidatePattern('^(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9])[.]){3}(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9]))(\/[1-3]?[1-9])?$')]
        [string]$Gateway,
		
        [Parameter(ParameterSetName = 'Network')]
        [ValidatePattern('^(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9])[.]){3}(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9]))$')]
        [string]$DnsServer1,
		
        [Parameter(ParameterSetName = 'Network')]
        [ValidatePattern('^(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9])[.]){3}(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9]))$')]
        [string]$DnsServer2,

        [Parameter(ParameterSetName = 'NetworkAdapter')]		
        [AutomatedLab.NetworkAdapter[]]$NetworkAdapter,
		
        [switch]$IsDomainJoined,
		
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [switch]$DefaultDomain,
		
        [System.Management.Automation.PSCredential]$InstallationUserCredential,
		
        [ValidatePattern("(?=^.{1,254}$)|([\'\""])(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.)+(?:[a-zA-Z]{2,})$)")]
        [string]$DomainName,
		
        [AutomatedLab.Role[]]$Roles,
		
        [string]$ProductKey,
		
        #Created ValidateSet using: "'" + ([System.Globalization.CultureInfo]::GetCultures([System.Globalization.CultureTypes]::InstalledWin32Cultures).Name -join "', '") + "'" | clip
        [ValidateSet('af', 'af-ZA', 'am', 'am-ET', 'ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-OM', 'ar-QA', 'ar-SA', 'ar-SY', 'ar-TN', 'ar-YE', 'arn', 'arn-CL', 'as', 'as-IN', 'az', 'az-Cyrl', 'az-Cyrl-AZ', 'az-Latn', 'az-Latn-AZ', 'ba', 'ba-RU', 'be', 'be-BY', 'bg', 'bg-BG', 'bn', 'bn-BD', 'bn-IN', 'bo', 'bo-CN', 'br', 'br-FR', 'bs', 'bs-Cyrl', 'bs-Cyrl-BA', 'bs-Latn', 'bs-Latn-BA', 'ca', 'ca-ES', 'ca-ES-valencia', 'chr', 'chr-Cher', 'chr-Cher-US', 'co', 'co-FR', 'cs', 'cs-CZ', 'cy', 'cy-GB', 'da', 'da-DK', 'de', 'de-AT', 'de-CH', 'de-DE', 'de-LI', 'de-LU', 'dsb', 'dsb-DE', 'dv', 'dv-MV', 'el', 'el-GR', 'en', 'en-029', 'en-AU', 'en-BZ', 'en-CA', 'en-GB', 'en-HK', 'en-IE', 'en-IN', 'en-JM', 'en-MY', 'en-NZ', 'en-PH', 'en-SG', 'en-TT', 'en-US', 'en-ZA', 'en-ZW', 'es', 'es-419', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-DO', 'es-EC', 'es-ES', 'es-GT', 'es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PR', 'es-PY', 'es-SV', 'es-US', 'es-UY', 'es-VE', 'et', 'et-EE', 'eu', 'eu-ES', 'fa', 'fa-IR', 'ff', 'ff-Latn', 'ff-Latn-SN', 'fi', 'fi-FI', 'fil', 'fil-PH', 'fo', 'fo-FO', 'fr', 'fr-BE', 'fr-CA', 'fr-CD', 'fr-CH', 'fr-CI', 'fr-CM', 'fr-FR', 'fr-HT', 'fr-LU', 'fr-MA', 'fr-MC', 'fr-ML', 'fr-RE', 'fr-SN', 'fy', 'fy-NL', 'ga', 'ga-IE', 'gd', 'gd-GB', 'gl', 'gl-ES', 'gn', 'gn-PY', 'gsw', 'gsw-FR', 'gu', 'gu-IN', 'ha', 'ha-Latn', 'ha-Latn-NG', 'haw', 'haw-US', 'he', 'he-IL', 'hi', 'hi-IN', 'hr', 'hr-BA', 'hr-HR', 'hsb', 'hsb-DE', 'hu', 'hu-HU', 'hy', 'hy-AM', 'id', 'id-ID', 'ig', 'ig-NG', 'ii', 'ii-CN', 'is', 'is-IS', 'it', 'it-CH', 'it-IT', 'iu', 'iu-Cans', 'iu-Cans-CA', 'iu-Latn', 'iu-Latn-CA', 'ja', 'ja-JP', 'ka', 'ka-GE', 'kk', 'kk-KZ', 'kl', 'kl-GL', 'km', 'km-KH', 'kn', 'kn-IN', 'ko', 'ko-KR', 'kok', 'kok-IN', 'ku', 'ku-Arab', 'ku-Arab-IQ', 'ky', 'ky-KG', 'lb', 'lb-LU', 'lo', 'lo-LA', 'lt', 'lt-LT', 'lv', 'lv-LV', 'mi', 'mi-NZ', 'mk', 'mk-MK', 'ml', 'ml-IN', 'mn', 'mn-Cyrl', 'mn-MN', 'mn-Mong', 'mn-Mong-CN', 'mn-Mong-MN', 'moh', 'moh-CA', 'mr', 'mr-IN', 'ms', 'ms-BN', 'ms-MY', 'mt', 'mt-MT', 'my', 'my-MM', 'nb', 'nb-NO', 'ne', 'ne-IN', 'ne-NP', 'nl', 'nl-BE', 'nl-NL', 'nn', 'nn-NO', 'no', 'nso', 'nso-ZA', 'oc', 'oc-FR', 'om', 'om-ET', 'or', 'or-IN', 'pa', 'pa-Arab', 'pa-Arab-PK', 'pa-IN', 'pl', 'pl-PL', 'prs', 'prs-AF', 'ps', 'ps-AF', 'pt', 'pt-BR', 'pt-PT', 'qut', 'qut-GT', 'quz', 'quz-BO', 'quz-EC', 'quz-PE', 'rm', 'rm-CH', 'ro', 'ro-MD', 'ro-RO', 'ru', 'ru-RU', 'rw', 'rw-RW', 'sa', 'sa-IN', 'sah', 'sah-RU', 'sd', 'sd-Arab', 'sd-Arab-PK', 'se', 'se-FI', 'se-NO', 'se-SE', 'si', 'si-LK', 'sk', 'sk-SK', 'sl', 'sl-SI', 'sma', 'sma-NO', 'sma-SE', 'smj', 'smj-NO', 'smj-SE', 'smn', 'smn-FI', 'sms', 'sms-FI', 'so', 'so-SO', 'sq', 'sq-AL', 'sr', 'sr-Cyrl', 'sr-Cyrl-BA', 'sr-Cyrl-CS', 'sr-Cyrl-ME', 'sr-Cyrl-RS', 'sr-Latn', 'sr-Latn-BA', 'sr-Latn-CS', 'sr-Latn-ME', 'sr-Latn-RS', 'st', 'st-ZA', 'sv', 'sv-FI', 'sv-SE', 'sw', 'sw-KE', 'syr', 'syr-SY', 'ta', 'ta-IN', 'ta-LK', 'te', 'te-IN', 'tg', 'tg-Cyrl', 'tg-Cyrl-TJ', 'th', 'th-TH', 'ti', 'ti-ER', 'ti-ET', 'tk', 'tk-TM', 'tn', 'tn-BW', 'tn-ZA', 'tr', 'tr-TR', 'ts', 'ts-ZA', 'tt', 'tt-RU', 'tzm', 'tzm-Latn', 'tzm-Latn-DZ', 'tzm-Tfng', 'tzm-Tfng-MA', 'ug', 'ug-CN', 'uk', 'uk-UA', 'ur', 'ur-IN', 'ur-PK', 'uz', 'uz-Cyrl', 'uz-Cyrl-UZ', 'uz-Latn', 'uz-Latn-UZ', 'vi', 'vi-VN', 'wo', 'wo-SN', 'xh', 'xh-ZA', 'yo', 'yo-NG', 'zh', 'zh-CN', 'zh-Hans', 'zh-Hant', 'zh-HK', 'zh-MO', 'zh-SG', 'zh-TW', 'zu', 'zu-ZA')]
        [string]$UserLocale,
		
        #Created ValidateSet using: "'" + (([System.TimeZoneInfo]::GetSystemTimeZones().Id | Sort-Object) -join "', '") + "'" | clip
        [ValidateSet('Afghanistan Standard Time', 'Alaskan Standard Time', 'Arab Standard Time', 'Arabian Standard Time', 'Arabic Standard Time', 'Argentina Standard Time', 'Atlantic Standard Time', 'AUS Central Standard Time', 'AUS Eastern Standard Time', 'Azerbaijan Standard Time', 'Azores Standard Time', 'Bahia Standard Time', 'Bangladesh Standard Time', 'Belarus Standard Time', 'Canada Central Standard Time', 'Cape Verde Standard Time', 'Caucasus Standard Time', 'Cen. Australia Standard Time', 'Central America Standard Time', 'Central Asia Standard Time', 'Central Brazilian Standard Time', 'Central Europe Standard Time', 'Central European Standard Time', 'Central Pacific Standard Time', 'Central Standard Time', 'Central Standard Time (Mexico)', 'China Standard Time', 'Dateline Standard Time', 'E. Africa Standard Time', 'E. Australia Standard Time', 'E. Europe Standard Time', 'E. South America Standard Time', 'Eastern Standard Time', 'Egypt Standard Time', 'Ekaterinburg Standard Time', 'Fiji Standard Time', 'FLE Standard Time', 'Georgian Standard Time', 'GMT Standard Time', 'Greenland Standard Time', 'Greenwich Standard Time', 'GTB Standard Time', 'Hawaiian Standard Time', 'India Standard Time', 'Iran Standard Time', 'Israel Standard Time', 'Jordan Standard Time', 'Kaliningrad Standard Time', 'Kamchatka Standard Time', 'Korea Standard Time', 'Libya Standard Time', 'Line Islands Standard Time', 'Magadan Standard Time', 'Mauritius Standard Time', 'Mid-Atlantic Standard Time', 'Middle East Standard Time', 'Montevideo Standard Time', 'Morocco Standard Time', 'Mountain Standard Time', 'Mountain Standard Time (Mexico)', 'Myanmar Standard Time', 'N. Central Asia Standard Time', 'Namibia Standard Time', 'Nepal Standard Time', 'New Zealand Standard Time', 'Newfoundland Standard Time', 'North Asia East Standard Time', 'North Asia Standard Time', 'Pacific SA Standard Time', 'Pacific Standard Time', 'Pacific Standard Time (Mexico)', 'Pakistan Standard Time', 'Paraguay Standard Time', 'Romance Standard Time', 'Russia Time Zone 10', 'Russia Time Zone 11', 'Russia Time Zone 3', 'Russian Standard Time', 'SA Eastern Standard Time', 'SA Pacific Standard Time', 'SA Western Standard Time', 'Samoa Standard Time', 'SE Asia Standard Time', 'Singapore Standard Time', 'South Africa Standard Time', 'Sri Lanka Standard Time', 'Syria Standard Time', 'Taipei Standard Time', 'Tasmania Standard Time', 'Tokyo Standard Time', 'Tonga Standard Time', 'Turkey Standard Time', 'Ulaanbaatar Standard Time', 'US Eastern Standard Time', 'US Mountain Standard Time', 'UTC', 'UTC+12', 'UTC-02', 'UTC-11', 'Venezuela Standard Time', 'Vladivostok Standard Time', 'W. Australia Standard Time', 'W. Central Africa Standard Time', 'W. Europe Standard Time', 'West Asia Standard Time', 'West Pacific Standard Time', 'Yakutsk Standard Time')]
        [string]$TimeZone,
		
        [AutomatedLab.PostInstallationActivity[]]$PostInstallationActivity,
		
        [string]$ToolsPath,
		
        [string]$ToolsPathDestination,
		
        [AutomatedLab.VirtualizationHost]$VirtualizationHost = 'HyperV',
		
        [switch]$EnableWindowsFirewall,

        [string]$AutoLogonDomainName,

        [string]$AutoLogonUserName,

        [string]$AutoLogonPassword,

        [hashtable]$AzureProperties,

        [hashtable]$HypervProperties,
        
        #Created ValidateSet using: "'" + ((Get-AzureRoleSize | Select-Object -ExpandProperty RoleSizeLabel | Sort-Object) -join "', '") + "'"
        [ValidateSet('A10 (8 cores, 57344 MB)', 'A11 (16 cores, 114688 MB)', 'A5 (2 cores, 14336 MB)', 'A6 (4 cores, 28672 MB)', 'A7 (8 cores, 57344 MB)', 'A8 (8 cores, 57344 MB)', 'A9 (16 cores, 114688 MB)', 'Basic_A0 (1 cores, 768 MB)', 'Basic_A1 (1 cores, 1792 MB)', 'Basic_A2 (2 cores, 3584 MB)', 'Basic_A3 (4 cores, 7168 MB)', 'Basic_A4 (8 cores, 14336 MB)', 'ExtraLarge (8 cores, 14336 MB)', 'ExtraSmall (1 cores, 768 MB)', 'Large (4 cores, 7168 MB)', 'Medium (2 cores, 3584 MB)', 'Small (1 cores, 1792 MB)', 'Standard_D1 (1 cores, 3584 MB)', 'Standard_D11 (2 cores, 14336 MB)', 'Standard_D12 (4 cores, 28672 MB)', 'Standard_D13 (8 cores, 57344 MB)', 'Standard_D14 (16 cores, 114688 MB)', 'Standard_D2 (2 cores, 7168 MB)', 'Standard_D3 (4 cores, 14336 MB)', 'Standard_D4 (8 cores, 28672 MB)', 'Standard_DS1 (1 cores, 3584 MB)', 'Standard_DS11 (2 cores, 14336 MB)', 'Standard_DS12 (4 cores, 28672 MB)', 'Standard_DS13 (8 cores, 57344 MB)', 'Standard_DS14 (16 cores, 114688 MB)', 'Standard_DS2 (2 cores, 7168 MB)', 'Standard_DS3 (4 cores, 14336 MB)', 'Standard_DS4 (8 cores, 28672 MB)', 'Standard_G1 (2 cores, 28672 MB)', 'Standard_G2 (4 cores, 57344 MB)', 'Standard_G3 (8 cores, 114688 MB)', 'Standard_G4 (16 cores, 229376 MB)', 'Standard_G5 (32 cores, 458752 MB)')]
        [string]$AzureRoleSize,

        [switch]$PassThru
    )

    $azurePropertiesValidKeys = 'CloudServiceName', 'UseAllRoleSizes', 'RoleSize'
    
    Write-LogFunctionEntry
    
    if (-not $VirtualizationHost -and -not (Get-LabDefinition).DefaultVirtualizationEngine)
    {
        Throw "Parameter 'VirtualizationHost' is mandatory when calling 'Add-LabMachineDefinition' if no default virtualization engine is specified"
    }
    
    if (-not (Get-LabDefinition))
    {
        throw 'Please create a lab definition by calling New-LabDefinition before adding machines'
    }

	$script:lab = Get-LabDefinition
	if (($script:lab.DefaultVirtualizationEngine -eq 'Azure' -or $VirtualizationHost -eq 'Azure') -and -not $script:lab.AzureSettings)
    {
        Add-LabAzurePublishSettingFile
    }
    
    if ($Global:labExported)
    {
        throw 'Lab is already exported. Please create a new lab definition by calling New-LabDefinition before adding machines'
    }

    if (Get-Lab -ErrorAction SilentlyContinue)
    {
        throw 'Lab is already imported. Please create a new lab definition by calling New-LabDefinition before adding machines'
    }

    if ($AzureProperties)
    {
        $illegalKeys = Compare-Object -ReferenceObject $azurePropertiesValidKeys -DifferenceObject ($AzureProperties.Keys | Select-Object -Unique) |
        Where-Object SideIndicator -eq '=>' |
        Select-Object -ExpandProperty InputObject

        if ($illegalKeys)
        {
            throw "The key(s) '$($illegalKeys -join ', ')' are not supported in AzureProperties. Valid keys are '$($azurePropertiesValidKeys -join ', ')'"
        }
    }

    if ($global:labNamePrefix) { $Name = "$global:labNamePrefix$Name" }
	
    if ($PSBoundParameters.ContainsKey('$DiskSizeInGb'))
    {
        Write-Warning -Message "The parameter 'DiskSizeInGb' is no longer in use and provided just for compatibility reasons"
    }
	
    if ($script:machines -eq $null)
    {
        $errorMessage = "Create a new lab first using 'New-LabDefinition' before adding machines"
        Write-Error $errorMessage
        Write-LogFunctionExitWithError -Message $errorMessage
        return
    }
	
    if ($script:machines | Where-Object Name -eq $Name)
    {
        $errorMessage = "A machine with the name '$Name' does already exist"
        Write-Error $errorMessage
        Write-LogFunctionExitWithError -Message $errorMessage
        return
    }
	
    if ($script:machines | Where-Object IpAddress.IpAddress -eq $IpAddress)
    {
        $errorMessage = "A machine with the IP address '$IpAddress' does already exist"
        Write-Error $errorMessage
        Write-LogFunctionExitWithError -Message $errorMessage
        return
    }
	
    $machine = New-Object AutomatedLab.Machine
    $machine.Name = $Name
    $machine.DiskSize = $DiskSizeInGb	
	
    if ((Get-LabDefinition).DefaultVirtualizationEngine -and (-not $PSBoundParameters.ContainsKey('VirtualizationHost')))
    {
        $VirtualizationHost = (Get-LabDefinition).DefaultVirtualizationEngine
    }
    
    if ($InstallationUserCredential)
    {
        $installationUser = New-Object AutomatedLab.User($InstallationUserCredential.UserName, $InstallationUserCredential.GetNetworkCredential().Password)
    }
    else
    {
        if ((Get-LabDefinition).DefaultInstallationCredential)
        {
            $installationUser = New-Object AutomatedLab.User((Get-LabDefinition).DefaultInstallationCredential.UserName, (Get-LabDefinition).DefaultInstallationCredential.Password)
        }
        else
        {
            switch ($VirtualizationHost)
            {
                'HyperV' { $installationUser = New-Object AutomatedLab.User('Administrator', 'Somepass1') }
                'Azure'  { $installationUser = New-Object AutomatedLab.User('Install', 'Somepass1') }
                Default  { $installationUser = New-Object AutomatedLab.User('Administrator', 'Somepass1') }
            }
        }
    }
    $machine.InstallationUser = $installationUser
    
    $machine.IsDomainJoined = $false
    
    if ($PSBoundParameters.ContainsKey('DefaultDomain') -and $DefaultDomain)
    {
        if (-not (Get-LabDomainDefinition))
        {
            if ($VirtualizationHost -eq 'Azure')
            {
                Add-LabDomainDefinition -Name 'contoso.com' -AdminUser Install -AdminPassword 'Somepass1'
            }
            else
            {
                Add-LabDomainDefinition -Name 'contoso.com' -AdminUser Administrator -AdminPassword 'Somepass1'
            }
        }
        
        $DomainName = (Get-LabDomainDefinition)[0].Name
    }
    
    if ($DomainName -or ($Roles -and $Roles.Name -in ('RootDC', 'FirstChildDC', 'DC')))
    {
        $machine.IsDomainJoined = $true

        if ($Roles.Name -eq 'RootDC' -or $Roles.Name -eq 'DC')
        {
            if (-not $DomainName)
            {
                throw 'Domain name not specified for Root Domain Controller'
            }
        }
        elseif ('FirstChildDC' -in $Roles.Name)
        {
            $role = $Roles | Where-Object Name -eq FirstChildDC
            $containsProperties = [boolean]$role.Properties
            if ($containsProperties)
            {
                $parentDomainInProperties = $role.Properties.ParentDomain
                $newDomainInProperties = $role.Properties.NewDomain
                
                Write-Verbose -Message "Machine contains custom properties for FirstChildDC: 'ParentDomain'='$parentDomainInProperties', 'NewDomain'='$newDomainInProperties'"
            }
            
            if ((-not $containsProperties) -and (-not $DomainName))
            {
                Write-Verbose -Message 'Nothing specified (no DomainName nor ANY Properties). Giving up'
		        
                throw 'Domain name not specified for Child Domain Controller'
            }
            
            if ((-not $DomainName) -and ((-not $parentDomainInProperties -or (-not $newDomainInProperties))))
            {
                Write-Verbose -Message 'No DomainName or Properties for ParentName and NewDomain specified. Giving up'
		        
                throw 'Domain name not specified for Child Domain Controller'
            }
            
            if ($containsProperties -and $parentDomainInProperties -and $newDomainInProperties -and (-not $DomainName))
            {
                Write-Verbose -Message 'Properties specified but DomainName is not. Then populate DomainName based on Properties'
                
                $DomainName = "$($role.Properties.NewDomain).$($role.Properties.ParentDomain)"
                Write-Verbose -Message "Machine contains custom properties for FirstChildDC but DomainName parameter is not specified. Setting now to '$DomainName'"
            }
            elseif (((-not $containsProperties) -or ($containsProperties -and (-not $parentDomainInProperties) -and (-not $newDomainInProperties))) -and $DomainName)
            {
                $newDomainName = $DomainName.Substring(0, $DomainName.IndexOf('.'))
                $parentDomainName = $DomainName.Substring($DomainName.IndexOf('.') + 1)

                Write-Verbose -Message 'No Properties specified (or properties for ParentName and NewDomain omitted) but DomainName parameter is specified. Calculating/updating ParentDomain and NewDomain properties'
                if (-not $containsProperties)
                {
                    $role.Properties = @{ 'NewDomain' = $newDomainName }
                    $role.Properties.Add('ParentDomain', $parentDomainName)
                }
                else
                {
                    if (-not $role.Properties.ContainsKey('NewDomain'))
                    {
                        $role.Properties.Add('NewDomain', $newDomainName)
                    }
                    
                    if (-not $role.Properties.ContainsKey('ParentDomain'))
                    {
                        $role.Properties.Add('ParentDomain', $parentDomainName)
                    }
                }
                $parentDomainInProperties = $role.Properties.ParentDomain
                $newDomainInProperties = $role.Properties.NewDomain
                Write-Verbose -Message "ParentDomain now set to '$parentDomainInProperties'"
                Write-Verbose -Message "NewDomain now set to '$newDomainInProperties'"
            }
        }
        
        if (-not (Get-LabDomainDefinition | Where-Object Name -eq $DomainName))
        {
            if ($VirtualizationHost -eq 'Azure')
            {
                Add-LabDomainDefinition -Name $DomainName -AdminUser Install -AdminPassword 'Somepass1'
            }
            else
            {
                Add-LabDomainDefinition -Name $DomainName -AdminUser Administrator -AdminPassword 'Somepass1'
            }
        }
        $machine.DomainName = $DomainName
    }
    
    switch ($OperatingSystem.Version.ToString(2))
    {
        '6.0'  { $level = 'Win2008' }
        '6.1'  { $level = 'Win2008R2' }
        '6.2'  { $level = 'Win2012' }
        '6.3'  { $level = 'Win2012R2' }
        '6.4'  { $level = 'WinThreshold' }
        '10.0' { $level = 'WinThreshold' }
    }

    $role = $roles | Where-Object Name -in ('RootDC', 'FirstChildDC', 'DC')
    if ($role)
    {
        if ($role.Properties)
        {
            if ($role.Name -eq 'RootDC')
            {
                if (-not $role.Properties.ContainsKey('ForestFunctionalLevel'))
                {
                    $role.Properties.Add('ForestFunctionalLevel', $level)
                }
            }
            
            if ($role.Name -eq 'RootDC' -or $role.Name -eq 'FirstChildDC')
            {
                if (-not $role.Properties.ContainsKey('DomainFunctionalLevel'))
                {
                    $role.Properties.Add('DomainFunctionalLevel', $level)
                }
            }
        }
        else
        {
            if ($role.Name -eq 'RootDC')
            {
                $role.Properties = @{'ForestFunctionalLevel' = $level}
                $role.Properties.Add('DomainFunctionalLevel', $level)
            }
            elseif ($role.Name -eq 'FirstChildDC')
            {
                $role.Properties = @{'DomainFunctionalLevel' = $level}
            }
        }
    }
    
    $role = $roles | Where-Object Name -eq Exchange2013
    if ($role)
    {
        if ($role.Properties)
        {
            if (-not $role.Properties.ContainsKey('OrganizationName'))
            {
                $role.Properties.Add('OrganizationName', 'ExOrg')
            }
        }
        else
        {
            $role.Properties = @{ 'OrganizationName' = 'ExOrg' }
        }

        $exchangeInstallUri = New-Object System.Uri((Get-Module AutomatedLab).PrivateData.Exchange2013DownloadLink)
        $exchangeInstallFileName = $ExchangeInstallUri.Segments[$ExchangeInstallUri.Segments.Count-1]
        $ucmaInstallUri = New-Object System.Uri((Get-Module AutomatedLab).PrivateData.ExchangeUcmaDownloadLink)
        $ucmaInstallFileName = $ucmaInstallUri.Segments[$ucmaInstallUri.Segments.Count-1]

        if ($machine.HostType -eq 'HyperV')
        {
			if (-not (Test-Path -Path "$(Get-LabSourcesLocation)\SoftwarePackages\$ucmaInstallFileName"))
			{
				throw "Could not find the Microsoft Unified Communications Managed API 4.0 file '$ucmaInstallFileName'. Please download it and pleace it in LabSources\SoftwarePackages"
			}
			if (-not (Test-Path -Path "$(Get-LabSourcesLocation)\SoftwarePackages\$exchangeInstallFileName"))
			{
				throw "Could not find the Microsoft Exchange 2013 installation file '$exchangeInstallFileName'. Please download it and pleace it in LabSources\SoftwarePackages"
			}
        }
    }
	
    if ($VirtualizationHost -eq 'Azure')
    {
        if (-not (Get-LabVirtualNetworkDefinition))
        {
            throw 'No virtual network is defined. Please call Add-LabVirtualNetworkDefinition before adding machines but after calling New-LabDefinition'
        }
    }
    elseif ($VirtualizationHost -eq 'HyperV')
    {
        if (-not (Get-LabVirtualNetworkDefinition))
        {
            $hostEntry = Get-HostEntry -HostName $Name -Section (Get-LabDefinition).Name
		    
            if ($hostEntry)
            {
                [int]$octet = $hostEntry.IpAddress.IPAddressToString.Split('.')[2]
            }
            else
            {
                [int]$octet = 2
                while ((Get-NetRoute -DestinationPrefix "192.168.$octet.0/24" -ErrorAction SilentlyContinue) -and ("192.168.$octet.1" -in (Get-NetIPAddress).IPAddress) -and $octet -le 254)
                {
                    $octet++
                }
            }
            
            if ($octet -le 254)
            {
                Add-LabVirtualNetworkDefinition -Name ((Get-LabDefinition).Name) -AddressSpace "192.168.$octet.1/24"
            }
            else
            {
                throw 'Virtual network could not be created. Please create virtual network manually by calling Add-LabVirtualNetworkDefinition (after calling New-LabDefinition)'
            }
            
            $script:autoIPAddress = ([AutomatedLab.IPAddress]("192.168.$octet.3")).AddressAsString
        }
        elseif (-not $script:autoIPAddress)
        {
            if ((Get-LabVirtualNetworkDefinition).AddressSpace)
            {
                $script:autoIPAddress = (Get-LabVirtualNetworkDefinition)[0].AddressSpace.FirstUsable.Increment().Increment()
            }
            else
            {
                [int]$octet = 2
                while ((Get-NetRoute -DestinationPrefix "192.168.$octet.0/24" -ErrorAction SilentlyContinue) -and ("192.168.$octet.1" -in (Get-NetIPAddress).IPAddress) -and $octet -le 254)
                {
                    $octet++
                }
                
                if ($octet -le 254)
                {
                    (Get-LabVirtualNetworkDefinition -Name (Get-LabDefinition).Name).AddressSpace = "192.168.$octet.0"
                }
                else
                {
                    throw 'Virtual network could not be created. Please create virtual network manually by calling Add-LabVirtualNetworkDefinition (after calling New-LabDefinition)'
                }
                
                $script:autoIPAddress = ([AutomatedLab.IPAddress]("192.168.$octet.3")).AddressAsString
            }
        }
    }
    
    if ($Network)
    {
        $networkDefinition = Get-LabVirtualNetworkDefinition | Where-Object Name -eq $network
        if (-not $networkDefinition)
        {
            throw "A virtual network definition with the name '$Network' could not be found. To get a list of network definitions, use 'Get-LabVirtualNetworkDefinition'"
        }

        $netAdapter = New-LabNetworkAdapterDefinition -VirtualSwitch $networkDefinition.Name
    }
    elseif (-not $NetworkAdapter)
    {
        if ((Get-LabVirtualNetworkDefinition).Count -eq 1)
        {
            $networkDefinition = Get-LabVirtualNetworkDefinition
			
            $netAdapter = New-LabNetworkAdapterDefinition -VirtualSwitch $networkDefinition.Name
        }
        else
        {
			throw "Network cannot be determined for machine '$machine'. Either no networks is defined or more than one network is defined while network is not specified when calling this function"
        }
    }
    
    $machine.HostType = $VirtualizationHost
	
    if (-not $IpAddress -and -not $NetworkAdapter)
    {
        if ($machine.HostType -eq 'HyperV')
        {
            $hostEntry = Get-HostEntry -HostName $Name -Section (Get-LabDefinition).Name
                         
            if ($hostEntry)
            {
                $ip = "$([AutomatedLab.IPAddress]$hostEntry.IpAddress.IPAddressToString)/$($networkDefinition.AddressSpace.Cidr)"
                $netAdapter.Ipv4Address.Add($ip)

                [void]$machine.NetworkAdapters.Add($netAdapter)
            }
            else
            {                    
                do
                {
                    $script:autoIPAddress = ([AutomatedLab.IPAddress]$script:autoIPAddress).Increment().AddressAsString 
                }
                until (-not (Get-HostEntry -IpAddress $script:autoIPAddress))
                                     
                $ip = [AutomatedLab.IPNetwork]"$($script:autoIPAddress)/$($networkDefinition.AddressSpace.Cidr)"

                $netAdapter.Ipv4Address.Add($ip)

                [void]$machine.NetworkAdapters.Add($netAdapter)
            }
        }
    }
    elseif ($NetworkAdapter)
    {
        $machine.NetworkAdapters.AddRange($NetworkAdapter)
    }
    elseif ($IpAddress)
    {
        if ($IpAddress.IndexOf('/') -ne -1)
        {
            $netAdapter.Ipv4Address.Add($IpAddress)
        }
        else
        {
            $netAdapter.Ipv4Address.Add("$IpAddress`/24")
        }
        
        [void]$machine.NetworkAdapters.Add($netAdapter)
    }

    if ($DnsServer1 -or $DnsServer2)
    {
        if ($DnsServer1) { $machine.NetWorkAdapters[0].Ipv4DnsServers.Add($DnsServer1) }
        if ($DnsServer2) { $machine.NetWorkAdapters[0].Ipv4DnsServers.Add($DnsServer2) }
    }
    else
    {
        #Azure does not support DNS servers, hence the placeholder for automatic DNS server assignment is not required
        if ($machine.HostType -ne 'Azure')
        {
            $machine.NetWorkAdapters[0].Ipv4DnsServers.Add('0.0.0.0')
        }
    }
    
    if ($Gateway) { $machine.NetworkAdapters[0].Ipv4Gateway = $Gateway }
    
    if ($processors -eq 0)
    {
        $processors = 1
        if (-not $script:processors) { $script:processors = (Get-WmiObject -Namespace Root\CIMv2 -Class win32_processor).NumberOfLogicalProcessors }
        if ($script:processors -ge 2)
        {
            $machine.Processors = 2
        }
    }
    else
    {
        $machine.Processors = $Processors
    }
    
    
    if ($PSBoundParameters.ContainsKey('MemoryInMb'))
    {
        $machine.Memory = $MemoryInMb
    }
    elseif ($PSBoundParameters.ContainsKey('Memory'))
    {
        $machine.Memory = [int]($Memory / 1MB)
    }
    else
    {
        $machine.Memory = 1
		
        #Memory weight based on role of machine
        $machine.Memory = 1
        foreach ($role in $Roles)
        {
            if ($PSCmdlet.MyInvocation.MyCommand.Module.PrivateData."MemoryWeight_$($role.Name)" -gt $machine.Memory)
            {
                $machine.Memory = $PSCmdlet.MyInvocation.MyCommand.Module.PrivateData."MemoryWeight_$($role.Name)"
            }
        }
    }
	
    if ($PSBoundParameters.ContainsKey('MaxMemoryInMb'))
    {
        $machine.MaxMemory = $MaxMemoryInMb
    }
    elseif ($PSBoundParameters.ContainsKey('MaxMemory'))
    {
        $machine.MaxMemory = [int]($MaxMemory/1MB)
    }

    
    $machine.EnableWindowsFirewall = $EnableWindowsFirewall
    
    $machine.AutoLogonDomainName = $AutoLogonDomainName
    $machine.AutoLogonUserName = $AutoLogonUserName
    $machine.AutoLogonPassword = $AutoLogonPassword
    
    if (-not $OperatingSystem)
    {
        throw "No operating system was defined for machine '$Name' and no default operating system defined. Please define either of these and retry. Call 'Get-LabAvailableOperatingSystem' to get a list of operating systems added to the lab."
    }
    
    if ($machine.HostType -eq 'HyperV')
    {
        if ($OperatingSystemVersion)
        {
            $os = $Script:lab.Sources.AvailableOperatingSystems | Where-Object { $_.OperatingSystemName -eq $OperatingSystem -and $_.Version -eq $OperatingSystemVersion }
        }
        else
        {
            $os = $Script:lab.Sources.AvailableOperatingSystems | Where-Object OperatingSystemName -eq $OperatingSystem
            if ($os.Count -gt 1)
            {
                $os = $os | Sort-Object Version -Descending | Select-Object -First 1
                Write-Warning "The operating system '$OperatingSystem' is available multiple times. Choosing the one with the highest version ($($os.Version))"
            }
        }
        
        if (-not $os)
        {
			if ($OperatingSystemVersion)
            {
                throw "The operating system '$OperatingSystem' for machine '$Name' with version '$OperatingSystemVersion' could not be found in the available operating systems. Call 'Get-LabAvailableOperatingSystem' to get a list of operating systems added to the lab."
            }
            else
            {
                throw "The operating system '$OperatingSystem' for machine '$Name' could not be found in the available operating systems. Call 'Get-LabAvailableOperatingSystem' to get a list of operating systems added to the lab."
            }
        }
        $machine.OperatingSystem = $os
    }
    elseif ($machine.HostType -eq 'Azure')
    {
        $machine.OperatingSystem = $OperatingSystem
    }
    
    if (-not $TimeZone)   { $TimeZone = tzutil.exe /g }
    $machine.Timezone = $TimeZone
    
    if (-not $UserLocale) { $UserLocale = (Get-Culture).Name }
    $machine.UserLocale = $UserLocale
    
    $machine.ProductKey = $ProductKey
    $machine.Roles = $Roles
    $machine.PostInstallationActivity = $PostInstallationActivity
    
    if ($HypervProperties)
    {
        $machine.HypervProperties = $HypervProperties
    }

    if ($AzureProperties)
    {
        $machine.AzureProperties = $AzureProperties
    }
    if ($AzureRoleSize)
    {
        if (-not $AzureProperties)
        {
            $machine.AzureProperties = @{ RoleSize = $AzureRoleSize }
        }
        else
        {
            $machine.AzureProperties.RoleSize = $AzureRoleSize
        }
    }

    $machine.ToolsPath = $ToolsPath.Replace('<machinename>', $machine.Name)
    
    $machine.ToolsPathDestination = $ToolsPathDestination

    $type = Get-Type -GenericType AutomatedLab.ListXmlStore -T AutomatedLab.Disk
    $machine.Disks = New-Object $type
    if ($DiskName)
    {
        foreach ($disk in $DiskName)
        {
            $labDisk = $script:disks | Where-Object Name -eq $disk
            if (-not $labDisk)
            {
                throw "The disk with the name '$disk' has not yet been added to the lab. Do this first using the cmdlet 'Add-LabDiskDefinition'"
            }
            $machine.Disks.Add($labDisk)
        }
    }
	
    $script:machines.Add($machine)
    $machineRoles = ''
    if ($machine.Roles) { $machineRoles = " (AutomatedLab roles: $($machine.Roles.Name -join ', '))" }
    Write-ScreenInfo -Message (("Added $VirtualizationHost machine definition '$Name'").PadRight(47) + $machineRoles)
	
    if ($PassThru)
    {
        $machine
    }
	
    Write-LogFunctionExit
}
#endregion Add-LabMachineDefinition

#region Get-LabMachineDefinition
function Get-LabMachineDefinition
{
    [CmdletBinding(DefaultParameterSetName = 'ByName')]
    [OutputType([AutomatedLab.Machine])]
	
    param (
        [Parameter(Position = 0, ParameterSetName = 'ByName', ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName,
		
        [Parameter(Mandatory, ParameterSetName = 'ByRole')]
        [AutomatedLab.Roles]$Role,
		
        [Parameter(Mandatory, ParameterSetName = 'All')]
        [switch]$All
    )
	
    begin
    {
        #required to suporess verbose messages, warnings and errors
        Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

        Write-LogFunctionEntry
		
        $result = @()
    }
	
    process
    {
        if ($PSCmdlet.ParameterSetName -eq 'ByName')
        {
            if ($ComputerName)
            {
                foreach ($n in $ComputerName)
                {
                    $machine = $Script:machines | Where-Object Name -in $n
                    if (-not $machine)
                    {
                        continue
                    }
				
                    $result += $machine
                }
            }
            else
            {
                $result = $Script:machines
            }
        }
		
        if ($PSCmdlet.ParameterSetName -eq 'ByRole')
        {
            $result = $Script:machines |
            Where-Object { $_.Roles.Name } |
            Where-Object { $_.Roles | Where-Object { $Role.HasFlag([AutomatedLab.Roles]$_.Name) } }
			
            if (-not $result)
            {
                return
            }
        }
		
        if ($PSCmdlet.ParameterSetName -eq 'All')
        {
            $result = $Script:machines
        }
    }
	
    end
    {
        $result
    }
}
#endregion Get-LabMachineDefinition

#region Remove-LabMachineDefinition
function Remove-LabMachineDefinition
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Name
    )
	
    Write-LogFunctionEntry
	
    $machine = $script:machines | Where-Object Name -eq $Name
	
    if (-not $machine)
    {
        Write-Warning "There is no machine defined with the name '$Name'"
    }
    else
    {
        [Void]$script:machines.Remove($machine)
        Write-Verbose "Machine '$Name' removed. Lab has $($Script:machines.Count) machine(s) defined"
    }
	
    Write-LogFunctionExit
}
#endregion Remove-LabMachineDefinition

#region Get-LabMachineRoleDefinition
function Get-LabMachineRoleDefinition
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [AutomatedLab.Roles]$Role,
		
        [hashtable]$Properties
    )
    
    $roleObjects = @()
    $availableRoles = [Enum]::GetNames([AutomatedLab.Roles])

    foreach ($availableRole in $availableRoles)
    {
        if ($Role.HasFlag([AutomatedLab.Roles]$availableRole))
        {
            $roleObject = New-Object -TypeName AutomatedLab.Role
            $roleObject.Name = $availableRole
            $roleObject.Properties = $Properties
			
            $roleObjects += $roleObject
        }
    }
	
    return $roleObjects
}
#endregion Get-LabMachineRoleDefinition

#region Get-LabPostInstallationActivity
function Get-LabPostInstallationActivity
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName = 'FileContentDependencyRemoteScript')]
        [Parameter(Mandatory, ParameterSetName = 'FileContentDependencyLocalScript')]
        [string]$DependencyFolder,
		
        [Parameter(Mandatory, ParameterSetName = 'IsoImageDependencyRemoteScript')]
        [Parameter(Mandatory, ParameterSetName = 'IsoImageDependencyLocalScript')]
        [string]$IsoImage,
		
        [Parameter(ParameterSetName = 'FileContentDependencyRemoteScript')]
        [Parameter(ParameterSetName = 'FileContentDependencyLocalScript')]
        [switch]$KeepFolder,
		
        [Parameter(Mandatory, ParameterSetName = 'FileContentDependencyRemoteScript')]
        [Parameter(Mandatory, ParameterSetName = 'IsoImageDependencyRemoteScript')]
        [string]$ScriptFileName,
		
        [Parameter(Mandatory, ParameterSetName = 'IsoImageDependencyLocalScript')]
        [Parameter(Mandatory, ParameterSetName = 'FileContentDependencyLocalScript')]
        [string]$ScriptFilePath,
		
        [switch]$UseCredSsp
    )
	
    Write-LogFunctionEntry
	
    $activity = New-Object -TypeName AutomatedLab.PostInstallationActivity
	
    if ($PSCmdlet.ParameterSetName -like 'FileContentDependency*')
    {
        $activity.DependencyFolder = $DependencyFolder
        $activity.KeepFolder = $KeepFolder.ToBool()
        if ($ScriptFilePath)
        {
            $activity.ScriptFilePath = $ScriptFilePath
        }
        else
        {
            $activity.ScriptFileName = $ScriptFileName
        }
    }
    elseif ($PSCmdlet.ParameterSetName -like 'IsoImage*')
    {
        $activity.IsoImage = $IsoImage
        if ($ScriptFilePath)
        {
            $activity.ScriptFilePath = $ScriptFilePath
        }
        else
        {
            $activity.ScriptFileName = $ScriptFileName
        }
    }
	
    $activity.UseCredSsp = $UseCredSsp
	
    Write-LogFunctionExit -ReturnValue $activity
    return $activity
}
#endregion Get-PostInstallationActivity
#endregion Machine Definition Functions

#region Get-DiskSpeed
function Get-DiskSpeed
{
    [CmdletBinding()]
    param (
        [ValidatePattern('[a-zA-Z]')]
        [Parameter(Mandatory)]
        [string]$DriveLetter,
		
        [ValidateRange(1, 50)]
        [int]$Interations = 1
    )
	
    $tempFileName = [System.IO.Path]::GetTempFileName()
	
    Invoke-Expression "winsat.exe disk -ran -read -count $Interations -drive $DriveLetter -xml $tempFileName | Out-Null"
    $readThroughoutRandom = (Select-Xml -Path $tempFileName -XPath '/WinSAT/Metrics/DiskMetrics/AvgThroughput').Node.'#text'
	
    Invoke-Expression "winsat disk -ran -write -count $Interations -drive $DriveLetter -xml $tempFileName | Out-Null"
    $writeThroughoutRandom = (Select-Xml -Path $tempFileName -XPath '/WinSAT/Metrics/DiskMetrics/AvgThroughput').Node.'#text'
	
    Remove-Item -Path $tempFileName
	
    $result = New-Object PSObject -Property ([ordered]@{
            ReadRandom = $readThroughoutRandom
            WriteRandom = $writeThroughoutRandom
    })
	
    $result
}
#endregion

#region Set-LabLocalVirtualMachineDiskAuto
function Set-LabLocalVirtualMachineDiskAuto
{
    [cmdletBinding()]
    param
    (
        [int64]$spaceNeeded
    )

    #Retrieve drives with enough space for placement of VMs
    $type = Get-Type -GenericType AutomatedLab.ListXmlStore -T AutomatedLab.LocalDisk
    $drives = New-Object $type
    Get-Partition | 
    Where-Object {$_.DiskNumber -in (Get-PhysicalDisk | Where-Object {$_.BusType -ne 'File Backed Virtual'} | Select-Object -ExpandProperty DeviceID)} |
    Where-Object { $_.DriveLetter} |
    Select-Object -ExpandProperty DriveLetter |
    Sort-Object |
    ForEach-Object { New-Object AutomatedLab.LocalDisk($_) } |
    Where-Object FreeSpaceGb -ge ([int]($spaceNeeded/1GB)) |
    ForEach-Object { $drives.Add($_) }
    #read the cache
    try
    {
        $importMethodInfo = $type.GetMethod('ImportFromRegistry', [System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::Static)
        $cachedDrives = $importMethodInfo.Invoke($null, ('Cache', 'LocalDisks'))
        Write-Verbose "Read $($cachedDrives.Count) drive infos from the cache"
    }
    catch
    {
        Write-Verbose 'Could not read info from the cache'
    }

    Write-Debug -Message "Drive letters placed on physical drives: $($drives.DriveLetter -Join ', ')"
    foreach ($drive in $drives)
    {
        Write-Debug -Message "Drive $drive free space: $($drive.FreeSpaceGb)GB)"
    }
        
    if (-not $drives)
    {
        return $false
    }
		
    if ($drives.Count -eq 1)
    {
        #Only one drive available for VMs
        Write-Verbose -Message 'Only one drive detected (or only one drive with sufficient space). Selecing this drive for placement of VMs'
        $script:lab.Target.Path = "$($drives[0].DriveLetter):\AutomatedLab-VMs"
    }
    else
    {
        #More than one drive available for VMs
			
        #Measure speed on drives found
        Write-Verbose -Message 'Measuring speed on fixed drives...'

        for ($i = 0; $i -lt $drives.Count; $i++)
        {
            $drive = $drives[$i]
            $drive.Serial = Get-Disk -Number (Get-Partition -DriveLetter $drive.DriveLetter).DiskNumber | Select-Object -ExpandProperty SerialNumber
            $drive.Signature = Get-Disk -Number (Get-Partition -DriveLetter $drive.DriveLetter).DiskNumber | Select-Object -ExpandProperty Signature

            if ($cachedDrives -contains $drive)
            {
                $drive = ($cachedDrives -eq $drive)[0]
                $drives[$drives.IndexOf($drive)] = $drive
                Write-Verbose -Message "(cached) Measurements for drive $drive (serial: $($drive.Serial)) (signature: $($drive.Signature)): Read=$([int]($drive.ReadSpeed)) MB/s  Write=$([int]($drive.WriteSpeed)) MB/s  Total=$([int]($drive.TotalSpeed)) MB/s"
            }
            else
            {
                $result = Get-DiskSpeed -DriveLetter $drive.DriveLetter
                $drive.ReadSpeed = $result.ReadRandom
                $drive.WriteSpeed = $result.WriteRandom
                    
                Write-Verbose -Message "Measurements for drive $drive (serial: $($drive.Serial)) (signature: $($drive.Signature)): Read=$([int]($drive.ReadSpeed)) MB/s  Write=$([int]($drive.WriteSpeed)) MB/s  Total=$([int]($drive.TotalSpeed)) MB/s"
            }
        }
        $drives.ExportToRegistry('Cache', 'LocalDisks')
			
        #creating a new list is required as otherwise $drives would be converted into an Object[]
        $drives = $drives | Sort-Object -Property TotalSpeed -Descending
        $bootDrive = $drives | Where-Object { (Get-Partition -DriveLetter $_.DriveLetter).IsBoot }
        if ($bootDrive)
        {
            Write-Verbose -Message "Boot drive is drive '$bootDrive'"
        }
        else
        {
            Write-Verbose -Message 'Boot drive is not part of the selected drive'
        }
    
        if ($drives[0] -ne $bootDrive)
        {
            #Fastest drive is not the boot drive. Selecting this drive!
            Write-Verbose -Message "Selecing drive $($drives[0].DriveLetter) for VMs based on speed and NOT being the boot drive"
            $script:lab.Target.Path = "$($drives[0].DriveLetter):\AutomatedLab-VMs"
        }
        else
        {
            #Fastest drive is the boot drive. If speed on next fastest drive is close to the boot drive in speed (within 50%), select this drive now instead of the boot drive
            #If not, select the boot drive
            if ((($drives[1].TotalSpeed - $drives[0].TotalSpeed) / $drives[1].TotalSpeed * 100) -lt 50)
            {
                Write-Verbose "Selecing drive $($drives[1].DriveLetter) for VMs based on speed and NOT being the boot drive"
                $script:lab.Target.Path = "$($drives[1].DriveLetter):\AutomatedLab-VMs"
            }
            else
            {
                Write-Verbose "Selecing drive $($drives[0].DriveLetter) for VMs based on speed though this drive is actually the boot drive but is much faster than second fastest drive ($($drives[1].DriveLetter))"
                $script:lab.Target.Path = "$($drives[0].DriveLetter):\AutomatedLab-VMs"
            }
        }
    }
    return $true
}
#endregion Set-LabLocalVirtualMachineDiskAuto